﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Wyse.Server.Logging;

namespace Wyse.Server.Base
{
    /// <summary>
    /// Server listener class
    /// </summary>
    public class Listener
    {
        /// <summary>
        /// Creates the listener
        /// </summary>
        public Listener(IPEndPoint endpoint, IClientContextFactory contextFactory, String name)
        {
            mListener = new TcpListener(endpoint);
            mCtxFactory = contextFactory;
            Name = name;
        }

        /// <summary>
        /// Gets the listener's lcoal endpoint
        /// </summary>
        public EndPoint EndPoint
        {
            get { return mListener.LocalEndpoint; }
        }

        /// <summary>
        /// Gets the name of this listener
        /// </summary>
        public String Name
        {
            get;
            private set;
        }

        /// <summary>
        /// Starts the listener
        /// </summary>
        public void Start()
        {
            mListener.Start();

            if (ConnectionLimit == 0)
                Log.Warning("No connection limit set");

            for (; ; )
            {
                try
                {
                    TcpClient client = mListener.AcceptTcpClient();

                    OnClientConnected(client);
                }
                catch(SocketException)
                {
                    break;
                }
            }
        }

        /// <summary>
        /// Stops the listener
        /// </summary>
        public void Stop()
        {
            mListener.Stop();

            lock (mClients)
            {
                // close all clients
                foreach (var cx in mClients)
                {
                    cx.Closed -= cx_Closed;
                    cx.Close();
                }

                mClients.Clear();
            }
        }

        /// <summary>
        /// Gets or sets the connection limit value
        /// </summary>
        public int ConnectionLimit
        {
            get;
            set;
        }

        /// <summary>
        /// Called when a new connection arrives
        /// </summary>
        /// <param name="result"></param>
        private void OnClientConnected(TcpClient client)
        {
            if (ConnectionLimit > 0 && mClients.Count >= ConnectionLimit)
            {
                // reject the client
                client.Close();

                Log.Print(LogLevel.Warning, "Client connection rejected: limit exceeded");

                return;
            }

            ClientContext cx = null;

            lock (mClients)
            {
                // create and start the context
                cx = mCtxFactory.CreateContext(client);

                mClients.Add(cx);

                Log.Print(LogLevel.Info, "Client ({0}/{1}) {2} connected", mClients.Count, ConnectionLimit, client.Client.RemoteEndPoint);
            }

            cx.Closed += cx_Closed;

            cx.Start();
        }

        /// <summary>
        /// Event fired when a context closes
        /// </summary>
        void cx_Closed(object sender, EventArgs e)
        {
            ClientContext cx = (ClientContext)sender;

            lock (mClients)
            {
                mClients.Remove(cx);
            }
        }

        private List<ClientContext> mClients = new List<ClientContext>();
        private IClientContextFactory mCtxFactory;
        private TcpListener mListener;
    }
}
